#include <cmath>
#include <GL/glew.h>
#include "UtilGL.h"
#include <iostream>
#include <string>
#include <sstream>

#include "Vector3.h"
#include "Matrix4.h"
#include "text3d.h"

/**


@author  F. Aubert

*/

using namespace prog3d;
using namespace std;

#define MY_PI 3.141592653


bool UtilGL::_isInitCubeBuffer=false;
GLuint UtilGL::_cubeVertexBuffer=0;
GLuint UtilGL::_cubeIndexBuffer=0;



void UtilGL::drawSphere() {
  GLUquadric *q=gluNewQuadric();
  gluSphere(q,1,10,10);
  gluDeleteQuadric(q);
}

void UtilGL::init() {
   t3dInit();
}

void UtilGL::draw(const Vector3 &p,int size) {
  glPushAttrib(GL_LIGHTING_BIT | GL_POINT_BIT);
  glDisable(GL_LIGHTING);
  glPointSize(size);
  glBegin(GL_POINTS);
  glVertex3dv(p.dv());
  glEnd();
  glPopAttrib();
}

void UtilGL::draw(const Vector3 &p1,const Vector3 &p2,int size) {
  glPushAttrib(GL_LIGHTING_BIT | GL_LINE_BIT);
  glDisable(GL_LIGHTING);
  glLineWidth(size);
  glBegin(GL_LINES);
  glVertex3dv(p1.dv());
  glVertex3dv(p2.dv());
  glEnd();
  glLineWidth(1);
  glPopAttrib();
}


void UtilGL::drawCone(double r,double h) {
      double theta=0.0; // angle total
      int numt=20;
      double past=2*MY_PI/(double)numt;

    double x0,y0;
    double nx0,ny0,nz0;
    nx0=h;ny0=0;nz0=r;
    double nx1,ny1,nz1;

    glBegin(GL_QUAD_STRIP);
    for(unsigned int i=0;i<=numt;i++) {
      x0=cos(theta)*r;
      y0=sin(theta)*r;

      nx1=cos(theta)*nx0-sin(theta)*ny0;
      ny1=sin(theta)*nx0+cos(theta)*ny0;
      nz1=nz0;
      glNormal3f(nx1,ny1,nz1);
      glVertex3f(x0,y0,0);
      glVertex3f(0,0,h);
      theta+=past;

    }
    glEnd();
}

void UtilGL::drawTorus(double rg,double rp) {
  // un peu bourrin, mais tant pis
      double theta=0.0; // angle total
      double phi=0.0;   // angle section
      int numt=20;
      int nump=10;
      double past=2*MY_PI/(double)numt;
      double pasp=2*MY_PI/(double)nump;

      double nx0,ny0,nz0,nx1,ny1,nz1;
      double x0,y0,z0; // rotation du point de section
      double x1,y1,z1; // point de section local section
      for(int i=0;i<numt;i++) {
        phi=0.0;
        glBegin(GL_QUAD_STRIP);
          for(int j=0;j<nump+1;j++) {
            nx1=cos(phi);
            ny1=0.0;
            nz1=sin(phi);
            x1=rp*nx1+(rg-rp);
            y1=0.0;
            z1=rp*nz1;
            for(int k=0;k<2;k++) {
              nx0=(nx1*cos(theta)-ny1*sin(theta));
              ny0=(nx1*sin(theta)+ny1*cos(theta));
              nz0=nz1;

              x0=(x1*cos(theta)-y1*sin(theta));
              y0=(x1*sin(theta)+y1*cos(theta));
              z0=z1;
              glNormal3f(nx0,ny0,nz0);
              glVertex3f(x0,y0,z0);
              theta+=past;
            }
            theta-=2.0*past;
            phi+=pasp;
          }
        glEnd();
        theta+=past;
      }
}







void UtilGL::initCubeBuffer() {
  if (! _isInitCubeBuffer) {

    float vertex[]={-1,-1,-1,-1,1,-1,1,-1,-1,1,1,-1,
                      -1,-1,1,-1,1,1,1,-1,1,1,1,1};

    unsigned int indice[]={0,1,2,3,6,7,4,5,0,1,
    5,7,1,3,
    6,4,2,0};

    glGenBuffers(1,&_cubeVertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER,_cubeVertexBuffer);
    glBufferData(GL_ARRAY_BUFFER,24*sizeof(GLfloat),vertex,GL_STATIC_DRAW);

    glGenBuffers(1,&_cubeIndexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,_cubeIndexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER,18*sizeof(GLuint),indice,GL_STATIC_DRAW);

    _isInitCubeBuffer=true;
  }
}


void UtilGL::drawImmediateSquare() {
  glBegin(GL_POLYGON);
  glNormal3f(0,0,1);
  glVertex2f(-1,-1);
  glVertex2f(1,-1);
  glVertex2f(1,1);
  glVertex2f(-1,1);
  glEnd();
}

void UtilGL::drawImmediateCube() {
  glPushMatrix();
  glTranslatef(0,0,1);
  drawImmediateSquare();
  glPopMatrix();

  glPushMatrix();
  glRotatef(180,0,1,0);
  glTranslatef(0,0,1);
  drawImmediateSquare();
  glPopMatrix();

  glPushMatrix();
  glRotatef(90,0,1,0);
  glTranslatef(0,0,1);
  drawImmediateSquare();
  glPopMatrix();

  glPushMatrix();
  glRotatef(-90,0,1,0);
  glTranslatef(0,0,1);
  drawImmediateSquare();
  glPopMatrix();

  glPushMatrix();
  glRotatef(90,1,0,0);
  glTranslatef(0,0,1);
  drawImmediateSquare();
  glPopMatrix();

  glPushMatrix();
  glRotatef(-90,1,0,0);
  glTranslatef(0,0,1);
  drawImmediateSquare();
  glPopMatrix();

}

void UtilGL::drawCubeBuffer() {
  glEnableClientState(GL_VERTEX_ARRAY);
  glBindBuffer(GL_ARRAY_BUFFER,_cubeVertexBuffer);
  glVertexPointer(3,GL_FLOAT,0,0);

  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,_cubeIndexBuffer);

  glDrawElements(GL_TRIANGLE_STRIP,10,GL_UNSIGNED_INT,(void *)(sizeof(GLuint)*0));
  glDrawElements(GL_TRIANGLE_STRIP,4,GL_UNSIGNED_INT,(void *)(sizeof(GLuint)*10));
  glDrawElements(GL_TRIANGLE_STRIP,4,GL_UNSIGNED_INT,(void *)(sizeof(GLuint)*14));
}

void UtilGL::drawCube(float taille) {

//  initCubeBuffer();


  glPushMatrix();
  glScaled(taille/2.0,taille/2.0,taille/2.0);
  drawImmediateCube();
//  drawCubeBuffer();
  glPopMatrix();
}



void UtilGL::drawCylinder() {
  GLUquadric *cylindre=gluNewQuadric();
  gluCylinder(cylindre,1,1,1,30,1);
  gluDeleteQuadric(cylindre);
}


// Matériel bleu-vert avec spéculaire vert
void UtilGL::materielBleuVert() {
  GLfloat ambient[4]= {0.1,0.1,0.1,1.0};
  GLfloat diffus[4]= {0,0.4,0.7,1.0}; // diffus bleu-vert
//  GLfloat speculaire[4]= {0,0.3,0.0,1.0}; // spéculaire vert
  GLfloat speculaire[4]= {0,0,0.0,0.0}; // pour tp 1 : pas de spéculaire
  GLfloat brillance=50;

  glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT,ambient);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,diffus);
  glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,speculaire);
  glMaterialf(GL_FRONT_AND_BACK,GL_SHININESS,brillance);
}


void UtilGL::drawDart(const Vector3 &a,const Vector3 &u,float dimension,const string &s1,const string &s2) {
  Vector3 to;
  to.set(a+u*(1.0-dimension));
  glLineWidth(3);
  glPushMatrix();

  glPushMatrix();
  glTranslatef(a.x(),a.y(),a.z());
  if (!s1.empty()) {
    draw(s1,Vector3(0,0,0));
  }
  glPopMatrix();


  glBegin(GL_LINES);
  glVertex3dv(a.dv());
  glVertex3dv(to.dv());
  glEnd();
  glLineWidth(1);

  if (u.length()>0.0001) {
  glPushMatrix();
  glTranslatef(to.x(),to.y(),to.z());

  if (!s2.empty()) {
    draw(s2,Vector3(0.2*dimension*u.length(),0.03,0));
  }


  Matrix4 m;
  m.setIdentity();
  m.setRotation(Vector3(0,0,1),u);

  glMultMatrixf(m.fv());



  drawCone(0.2*dimension*u.length(),u.length()*dimension);




  glPopMatrix();
  }

  glPopMatrix();

}


void UtilGL::materialRed() {
  GLfloat ambient[4]= {0.1,0.1,0.1,1.0};
  GLfloat diffus[4]= {0.8,0.4,0.0,1.0}; // diffus bleu-vert
  GLfloat speculaire[4]= {0.4,0.4,0.4,1.0}; // spéculaire vert
  GLfloat brillance=50;

  glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT,ambient);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,diffus);
  glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,speculaire);
  glMaterialf(GL_FRONT_AND_BACK,GL_SHININESS,brillance);
}




void UtilGL::drawAxes(float taille) {
  glPushAttrib(GL_LIGHTING_BIT);
  glDisable(GL_LIGHTING);
  Vector3 o(0,0,0);
  Vector3 u;
  glPushMatrix();
  u.set(1,0,0);
  glColor3f(1,0,0);
  drawDart(o,u,taille);
  u.set(0,1,0);
  glColor3f(0,1,0);
  drawDart(o,u,taille);
  u.set(0,0,1);
  glColor3f(0,0,1);
  drawDart(o,u,taille);
  glPopMatrix();
  glPopAttrib();
}

void UtilGL::drawGrid() {
  glColor3f(0,0,0);

  float pasX;
  float pasY;
  float demiLongueur=2;  // les coordonnées varient entre -2 et 2 (taille de la grille en (x,y) est 4 x 4.
  int nbCase=20;       // nbre de cases de la grille en x et y : taille de la grille en cases : nbCase x nbCase
  float posX,posY;

  pasX=demiLongueur*2.0/float(nbCase);
  pasY=demiLongueur*2.0/float(nbCase);

  posX=-demiLongueur;
  for(int i=0; i<nbCase; i++) {
    posY=-demiLongueur;
    glBegin(GL_QUAD_STRIP);
    for(int j=0; j<nbCase+1; j++) {
      glVertex2f(posX,posY);
      glVertex2f(posX+pasX,posY);


      posY+=pasY;
    }
    glEnd();
    posX+=pasX;
  }

}

void UtilGL::drawGround() {
  glPushAttrib(GL_POLYGON_BIT | GL_LIGHTING_BIT);
  glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
  glDisable(GL_LIGHTING);
  glPushMatrix();
  glRotatef(-90,1,0,0);
  glTranslatef(0,0,-3);
  glScalef(5,5,1);
  drawGrid();
  glPopMatrix();
  glPopAttrib();
}


void UtilGL::drawSegment(const Vector3 &a,const Vector3 &b) {
    glPushAttrib(GL_LIGHTING_BIT);
    glDisable(GL_LIGHTING);
    glBegin(GL_LINES);
    glVertex3dv(a.dv());
    glVertex3dv(b.dv());
    glEnd();
    glPopAttrib();
}

void UtilGL::draw(string s,float x,float y,double height) {
  glPushAttrib(GL_LIGHTING_BIT | GL_POLYGON_BIT | GL_LINE_BIT);
  glDisable(GL_LIGHTING);
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glOrtho(-1,1,-1,1,-1,1);
  glTranslatef(x,y,0);
  glMatrixMode(GL_MODELVIEW);

  double heightChar=t3dDrawHeight(s);
  glScaled(height/heightChar,height/heightChar,1.0);
  glLineWidth(1);
  glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
  t3dDraw2D(s, -1, 1,height);
  /*
  for(unsigned int i=0; i<s.size(); i++) {
//    glutStrokeCharacter(GLUT_STROKE_MONO_ROMAN,s[i]);
    glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12,s[i]);
  }
  */
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();
  glPopAttrib();
}

void UtilGL::initLight(GLint light) {
  GLfloat ambient[4]= {1.0,1.0,1.0,1.0};
  GLfloat diffuse[4]= {1.0,1.0,1.0,1.0};
  GLfloat speculaire[4]= {.4,.4,.4,1};

  glEnable(light);
  glLightfv(light,GL_AMBIENT,ambient);
  glLightfv(light,GL_DIFFUSE,diffuse);
  glLightfv(light,GL_SPECULAR,speculaire);
}

void UtilGL::materialFrontBack() {
  GLfloat ambientf[4]= {0.9,0.0,0.0,1.0};
  GLfloat diffusf[4]= {0,0.0,0.0,1.0};
  GLfloat speculairef[4]= {0,0.0,0.0,1.0};
  GLfloat brillance=50;

  glMaterialfv(GL_FRONT,GL_AMBIENT,ambientf);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,diffusf);
  glMaterialfv(GL_FRONT,GL_SPECULAR,speculairef);
  glMaterialf(GL_FRONT_AND_BACK,GL_SHININESS,brillance);

  GLfloat ambientb[4]= {0.0,0.9,0,1.0};
  GLfloat diffusb[4]= {0.0,0.0,0,1.0};
  GLfloat speculaireb[4]= {0,0.0,0.0,1.0};

  glMaterialfv(GL_BACK,GL_AMBIENT,ambientb);
  glMaterialfv(GL_BACK,GL_DIFFUSE,diffusb);
  glMaterialfv(GL_BACK,GL_SPECULAR,speculaireb);
}

void UtilGL::draw(const vector<Vector3> &l1,const vector<Vector3> &l2) {
  glBegin(GL_QUAD_STRIP);
  for(unsigned int i=0;i<l1.size();i++) {
    glVertex3dv(l1[i].dv());
    glVertex3dv(l2[i].dv());
  }
  glEnd();
}

void UtilGL::draw(unsigned int i,const Vector3 &u) {
  ostringstream oss;
  oss << i;
  draw(oss.str(),u);

}

void UtilGL::draw(const std::string &s,const Vector3 &u,double dimension) {
  glPushAttrib(GL_LIGHTING_BIT | GL_POLYGON_BIT | GL_LINE_BIT);
  glLineWidth(1);
  glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);


  glPushMatrix();
  glTranslatef(u.x(),u.y(),u.z());
  glScalef(dimension,dimension,1);
  t3dDraw2D(s,-1,1,0.02);
  glPopMatrix();

  glPopAttrib();

  // TODO
}


